/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012-2013 Oliver Katz */

#include "homogenizer.h"

z_size z_homogenizer_estimate_size(z_data *d);

z_size z_homogenizer_estimate_size_assoc(_z_assoc a)
{
	z_size rtn = z_homogenizer_estimate_size(&(a.content));
	for (int i = 0; i < 256; i++)
		if (a.branches[i] != NULL)
			rtn += z_homogenizer_estimate_size_assoc(*(a.branches[i]));
	return rtn;
}

z_size z_homogenizer_estimate_size(z_data *d)
{
	Z_WARNING_DATA(d, 0);

	z_size rtn = 0;

	rtn += sizeof(z_data);
	rtn += z_data_size_get(d);

	if (z_data_typeid_get(d) == Z_TYPEID_STR)
	{
		rtn += z_string_size(d)*sizeof(char);
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_VECTOR)
	{
		for (int i = 0; i < z_vector_size(d); i++)
			rtn += z_homogenizer_estimate_size(z_vector_at(d, i));
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_ASSOC)
	{
		rtn += z_homogenizer_estimate_size_assoc(*(_z_assoc *)z_data_src_get(d));
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_PAIR)
	{
		rtn += z_homogenizer_estimate_size(z_pair_first(d));
		rtn += z_homogenizer_estimate_size(z_pair_second(d));
	}

	return rtn;
}

void z_homogenize_core(z_data *d, void **head);

void z_homogenize_core_assoc(_z_assoc *a, void **head)
{
	z_homogenize_core(&(a->content), head);
	for (int i = 0; i < 256; i++)
	{
		if (a->branches[i] != NULL)
		{
			z_homogenize_core_assoc(a->branches[i], head);
		}
	}
}

void z_homogenize_core(z_data *d, void **head)
{
	if (z_data_typeid_get(d) == Z_TYPEID_STR)
	{
		_z_string *tmp = (_z_string *)z_data_src_get(d);
		memcpy(*head, tmp->content, tmp->size*sizeof(char));
		tmp->content = *head;
		*head += tmp->size*sizeof(char);
		memcpy(*head, tmp, sizeof(_z_string));
		tmp = *head;
		z_data_src_set(d, tmp);
		*head += sizeof(_z_string);
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_VECTOR)
	{
		_z_vector *tmp = (_z_vector *)z_data_src_get(d);
		for (int i = 0; i < tmp->head-tmp->content; i++)
			z_homogenize_core(tmp->content+i, head);
		memcpy(*head, tmp->content, tmp->size*sizeof(z_data));
		tmp->content = *head;
		tmp->head += (void *)(*head)-(void *)(tmp->content);
		*head += tmp->size*sizeof(z_data);
		memcpy(*head, tmp, sizeof(_z_vector));
		tmp = *head;
		z_data_src_set(d, tmp);
		*head += sizeof(_z_vector);
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_ASSOC)
	{
		z_homogenize_core_assoc((_z_assoc *)z_data_src_get(d), head);
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_PAIR)
	{
		_z_pair *tmp = (_z_pair *)z_data_src_get(d);
		z_homogenize_core(&(tmp->first), head);
		z_homogenize_core(&(tmp->second), head);
		memcpy(*head, tmp, sizeof(_z_pair));
		tmp = *head;
		*head += sizeof(_z_pair);
	}

	memcpy(*head, z_data_src_get(d), z_data_size_get(d));
	z_data_src_set(d, *head);
	*head += z_data_size_get(d);
}

void z_homogenize(z_data **d)
{
	z_size s = z_homogenizer_estimate_size(*d)+1;
	void *buf = malloc(s);
	memset(buf, 0, s);

	void *head = buf;

	z_homogenize_core(*d, &head);

	memcpy(head, *d, sizeof(z_data));
	*d = head;
	head += sizeof(z_data);
}

void z_rebase(z_data *d, void *a, void *b);

void z_rebase_assoc(_z_assoc *r, void *a, void *b)
{
	z_rebase(&(r->content), a, b);
	for (int i = 0; i < 256; i++)
		if (r->branches[i] != NULL)
			z_rebase_assoc(r->branches[i], a, b);
}

void z_rebase(z_data *d, void *a, void *b)
{
	z_data_src_set(d, (void *)((int)z_data_src_get(d)+(int)a-(int)b));

	if (z_data_typeid_get(d) == Z_TYPEID_STR)
	{
		_z_string *tmp = (_z_string *)z_data_src_get(d);
		tmp->content += a-b;
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_VECTOR)
	{
		_z_vector *tmp = (_z_vector *)z_data_src_get(d);
		tmp->content += a-b;
		tmp->head += a-b;

		for (int i = 0; i < tmp->head-tmp->content; i++)
			z_rebase(tmp->content+i, a, b);
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_PAIR)
	{
		z_rebase(z_pair_first(d), a, b);
		z_rebase(z_pair_second(d), a, b);
	}
	else if (z_data_typeid_get(d) == Z_TYPEID_ASSOC)
	{
		_z_assoc *tmp = (_z_assoc *)z_data_src_get(d);
		z_rebase_assoc(tmp, a, b);
	}
}

